package com.hy.video.monitor.gb28181.transmit.event.request;

import com.hy.video.monitor.gb28181.bean.ParentPlatform;
import gov.nist.javax.sip.SipProviderImpl;
import gov.nist.javax.sip.SipStackImpl;
import gov.nist.javax.sip.message.SIPRequest;
import gov.nist.javax.sip.stack.SIPServerTransaction;
import org.apache.commons.lang3.ArrayUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

import javax.sip.*;
import javax.sip.address.Address;
import javax.sip.address.AddressFactory;
import javax.sip.address.SipURI;
import javax.sip.header.ContentTypeHeader;
import javax.sip.header.ExpiresHeader;
import javax.sip.header.HeaderFactory;
import javax.sip.header.ViaHeader;
import javax.sip.message.MessageFactory;
import javax.sip.message.Request;
import javax.sip.message.Response;
import java.io.ByteArrayInputStream;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**    
 * @description:处理接收IPCamera发来的SIP协议请求消息
 * @author: KangJonney
 * @date:   2020年5月3日 下午4:42:22     
 */
public abstract class SIPRequestProcessorParent {

	private final static Logger logger = LoggerFactory.getLogger(SIPRequestProcessorParent.class);

	@Autowired
	@Qualifier(value="tcpSipProvider")
	private SipProviderImpl tcpSipProvider;

	@Autowired
	@Qualifier(value="udpSipProvider")
	private SipProviderImpl udpSipProvider;

	/**
	 * 根据 RequestEvent 获取 ServerTransaction
	 * @param evt
	 * @return
	 */
	public ServerTransaction getServerTransaction(RequestEvent evt) {
		/**
		 * Request / Response 表示 SIP 请求、响应。两者都是 Message 接口的子接口。可通过它们访问 SIP 消息的头、内容及其他部分。
		 */
		Request request = evt.getRequest();
		ServerTransaction serverTransaction = evt.getServerTransaction();
		// 判断TCP还是UDP
		boolean isTcp = false;
		/**
		 * 获取此消息中指定名称的标头。 如果消息中存在多个此报头名称的header，则返回消息中的第一个报头。
		 * 参数:
		 * headerName -请求的报头类型的新字符串名称。
		 * 返回:
		 * 消息中指定名称的报头，如果报头不存在，此方法返回null。
		 */
		ViaHeader reqViaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME);
		/**
		 * 返回传输参数的值。
		 * 返回:
		 * ViaHeader的传输参数的字符串值  tcp / udp / sctp / tls
		 */
		String transport = reqViaHeader.getTransport();
		if (transport.equals("TCP")) {
			isTcp = true;
		}
		/**
		 * 对应response响应的客户端事务：ClientTransaction
		 * 对应request请求的服务端事务：ServerTransaction
		 */
		if (serverTransaction == null) {
			try {
				if (isTcp) {
					SipStackImpl stack = (SipStackImpl)tcpSipProvider.getSipStack();
					serverTransaction = (SIPServerTransaction) stack.findTransaction((SIPRequest)request, true);
					if (serverTransaction == null) {
						serverTransaction = tcpSipProvider.getNewServerTransaction(request);
					}
				} else {
					SipStackImpl stack = (SipStackImpl)udpSipProvider.getSipStack();
					serverTransaction = (SIPServerTransaction) stack.findTransaction((SIPRequest)request, true);
					if (serverTransaction == null) {
						serverTransaction = udpSipProvider.getNewServerTransaction(request);
					}
				}
			} catch (TransactionAlreadyExistsException e) {
				logger.error(e.getMessage());
			} catch (TransactionUnavailableException e) {
				logger.error(e.getMessage());
			}
		}
		return serverTransaction;
	}

	/**
	 * SipURI requestURI = addressFactory.createSipURI(username, address);
	 *
	 * @return
	 */
	public AddressFactory getAddressFactory() {
		try {
			return SipFactory.getInstance().createAddressFactory();
		} catch (PeerUnavailableException e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * via 等头信息
	 *
	 * @return
	 */
	public HeaderFactory getHeaderFactory() {
		try {
			return SipFactory.getInstance().createHeaderFactory();
		} catch (PeerUnavailableException e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 生产Message对象：Message 接口是 SIP 消息的基接口，子接口：Request和Response
	 *
	 * @return
	 */
	public MessageFactory getMessageFactory() {
		try {
			return SipFactory.getInstance().createMessageFactory();
		} catch (PeerUnavailableException e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 回复状态码
	 * 100 trying 临时响应，表示已经接收到请求消息，正在对其进行处理
	 * 200 OK 成功，表示请求已经被成功接收、处理
	 * 3xx：重定向，表示需要采取进一步动作，以完成该请求
	 * 400 客户端错误
	 * 404 客户端错误
	 * 5xx：服务器端错误，表示SIP服务器故障不能完成对正确消息的处理
	 * 6xx：全局错误，表示请求不能在任何SIP服务器上实现
	 *
	 * @param evt
	 * @param statusCode 响应码
	 *                   <p>
	 *                   SIP消息可以被分为两类
	 *                   请求
	 *                   发起一个会话
	 *                   响应
	 *                   对请求的响应
	 *                   SIP请求消息类型：
	 *                   Invite:发起会话请求
	 *                   Ack:对Invite请求的响应确认
	 *                   Cancel:取消尚未完成的请求
	 *                   Register:注册、完成地址的绑定
	 *                   Options：查询服务器的能力
	 *                   SIP扩展请求消息类型
	 *                   Message:请求一个即时消息
	 *                   Subscribe:签订一个通知事件
	 *                   Notify:发送一个通知事件
	 *                   Update:在建立呼叫阶段修改会话属性
	 *                   Publish:发送事件状态给状态服务器
	 *                   Prack:标识临时响应的可靠性
	 */
	public void responseAck(RequestEvent evt, int statusCode) throws SipException, InvalidArgumentException, ParseException {
		Response response = getMessageFactory().createResponse(statusCode, evt.getRequest());
		ServerTransaction serverTransaction = getServerTransaction(evt);
		if (serverTransaction == null) {
			logger.warn("回复失败：{}", response);
			return;
		}
		//发送响应请求
		serverTransaction.sendResponse(response);
		//NOTIFY:表示一个通知的动作
		if (statusCode >= 200 && !"NOTIFY".equals(evt.getRequest().getMethod())) {

			if (serverTransaction.getDialog() != null) serverTransaction.getDialog().delete();
		}
	}

	public void responseAck(RequestEvent evt, int statusCode, String msg) throws SipException, InvalidArgumentException, ParseException {
		Response response = getMessageFactory().createResponse(statusCode, evt.getRequest());
		//易读的状态码说明的访问器
		response.setReasonPhrase(msg);
		ServerTransaction serverTransaction = getServerTransaction(evt);
		serverTransaction.sendResponse(response);
		if (statusCode >= 200 && !"NOTIFY".equals(evt.getRequest().getMethod())) {
			if (serverTransaction.getDialog() != null) serverTransaction.getDialog().delete();
		}
	}

	/**
	 * 回复带sdp的200
	 * @param evt
	 * @param sdp
	 * @throws SipException
	 * @throws InvalidArgumentException
	 * @throws ParseException
	 */
	public void responseSdpAck(RequestEvent evt, String sdp, ParentPlatform platform) throws SipException, InvalidArgumentException, ParseException {
		Response response = getMessageFactory().createResponse(Response.OK, evt.getRequest());
		SipFactory sipFactory = SipFactory.getInstance();
		ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("APPLICATION", "SDP");
		response.setContent(sdp, contentTypeHeader);
		/**
		 * 请求URI的访问器，即SIP请求的第一行。通常，这是SipURI的实例
		 * contentAddress-> INVITE sip:34020000001320000002@10.10.20.173:5060 SIP/2.0
		 */
		// 兼容国标中的使用编码@域名作为RequestURI的情况
		SipURI sipURI = (SipURI)evt.getRequest().getRequestURI();
		if (sipURI.getPort() == -1) {
			sipURI = sipFactory.createAddressFactory().createSipURI(platform.getServerGBId(),  platform.getServerIP()+":"+platform.getServerPort());
		}
		logger.debug("responseSdpAck SipURI: {}:{}", sipURI.getHost(), sipURI.getPort());

		Address concatAddress = sipFactory.createAddressFactory().createAddress(
				sipFactory.createAddressFactory().createSipURI(sipURI.getUser(),  sipURI.getHost()+":"+sipURI.getPort()
				));
		response.addHeader(sipFactory.createHeaderFactory().createContactHeader(concatAddress));
		getServerTransaction(evt).sendResponse(response);
	}

	/**
	 * 回复带xml的200
	 * @param evt
	 * @param xml
	 * @throws SipException
	 * @throws InvalidArgumentException
	 * @throws ParseException
	 */
	public Response responseXmlAck(RequestEvent evt, String xml) throws SipException, InvalidArgumentException, ParseException {
		Response response = getMessageFactory().createResponse(Response.OK, evt.getRequest());
		SipFactory sipFactory = SipFactory.getInstance();
		ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("APPLICATION", "MANSCDP+xml");
		response.setContent(xml, contentTypeHeader);

		SipURI sipURI = (SipURI)evt.getRequest().getRequestURI();

		Address concatAddress = sipFactory.createAddressFactory().createAddress(
				sipFactory.createAddressFactory().createSipURI(sipURI.getUser(),  sipURI.getHost()+":"+sipURI.getPort()
				));
		response.addHeader(sipFactory.createHeaderFactory().createContactHeader(concatAddress));
		//添加响应头信息-> Expires: 3600
		response.addHeader(evt.getRequest().getHeader(ExpiresHeader.NAME));
		getServerTransaction(evt).sendResponse(response);
		return response;
	}
	/**
	 * 解析xml：请求 / 应答 消息内容
	 *
	 * @param evt
	 * @return
	 */
	public Element getRootElement(RequestEvent evt) throws DocumentException {
		return getRootElement(evt, "gb2312");
	}
	public Element getRootElement(RequestEvent evt, String charset) throws DocumentException {
		if (charset == null) charset = "gb2312";
		Request request = evt.getRequest();
		SAXReader reader = new SAXReader();
		reader.setEncoding(charset);
		// 对海康出现的未转义字符做处理。
		String[] destStrArray = new String[]{"&lt;","&gt;","&amp;","&apos;","&quot;"};
		char despChar = '&'; // 或许可扩展兼容其他字符
		byte destBye = (byte) despChar;
		List<Byte> result = new ArrayList<>();
		/**
		 * 以字节数组的形式获取消息的主体内容。
		 * 返回:
		 * 将Message的正文内容作为一个字节数组，如果正文不存在，此方法返回null。
		 */
		byte[] rawContent = request.getRawContent();
		for (int i = 0; i < rawContent.length; i++) {
			if (rawContent[i] == destBye) {
				boolean resul = false;
				for (String destStr : destStrArray) {
					if (i + destStr.length() <= rawContent.length) {
						byte[] bytes = Arrays.copyOfRange(rawContent, i, i + destStr.length());
						resul = resul || (Arrays.equals(bytes,destStr.getBytes()));
					}
				}
				if (resul) {
					result.add(rawContent[i]);
				}
			}else {
				result.add(rawContent[i]);
			}
		}
		Byte[] bytes = new Byte[0];
		byte[] bytesResult = ArrayUtils.toPrimitive(result.toArray(bytes));

		Document xml = reader.read(new ByteArrayInputStream(bytesResult));
		return xml.getRootElement();
	}

}
